iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0

Sync是golang蠻常使用的package

第一個例子是WaitGroup,原始碼的註解寫得很清楚,總之透過WaitGroup可以控制goruotine的運行
// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.

func main() {
	var wg sync.WaitGroup

	wg.Add(1) // <1>
	go func() {
		defer wg.Done() // <2>
		fmt.Println("1st goroutine sleeping...")
		time.Sleep(1)
	}()

	wg.Add(1) // <1>
	go func() {
		defer wg.Done() // <2>
		fmt.Println("2nd goroutine sleeping...")
		time.Sleep(2)
	}()

	wg.Wait() // <3>
	fmt.Println("All goroutines complete.")
}
主 goroutine(main) 啟動並設定了 WaitGroup。
啟動第一個 goroutine 並讓其睡眠 1 秒。
啟動第二個 goroutine 並讓其睡眠 2 秒。
主 goroutine 會因 wg.Wait() 而阻塞,直到所有其他 goroutines 完成執行。
當所有 goroutines 都呼叫了 wg.Done(),WaitGroup 的計數器歸零,主 goroutine 繼續執行並印出 "All goroutines complete."。
注意:在這個例子中,由於第二個 goroutine 的睡眠時間比第一個長,所以 "1st goroutine sleeping..." 總是先印出,接著是 "2nd goroutine sleeping...",最後是 "All goroutines complete."。

Mutex, RWMutex

基本功能:
Mutex:Mutex(互斥鎖)用於確保同一時刻只有一個 goroutine 可以訪問某個區段的代碼或數據。當一個 goroutine 試圖鎖定已被其他 goroutine 鎖定的 Mutex 時,它將被阻塞,直到該 Mutex 變得可用。
RWMutex:RWMutex(讀-寫互斥鎖)有兩種鎖定模式:讀取鎖和寫入鎖。讀取鎖允許多個 goroutines 同時訪問,但寫入鎖確保同一時間只有一個 goroutine 可以寫入或修改資料,且在寫入鎖定時不允許任何其他讀取或寫入操作。

使用場景:
Mutex:當你只需要確保在某一時間只有一個 goroutine 訪問某個資源時,使用 Mutex。
RWMutex:當讀取操作遠多於寫入操作,且你希望允許多個 goroutines 同時進行讀取操作時,使用 RWMutex 會更有效率。

性能:
在只有少量的寫入和大量的讀取操作時,RWMutex 可以提供更好的性能,因為它允許多個讀取者同時進行,而不會互相阻塞。
然而,在高度競爭的狀況下,RWMutex 可能不如普通的 Mutex 高效,因為它的內部結構更為複雜。

func main() {
	producer := func(wg *sync.WaitGroup, l sync.Locker) { // <1>
		defer wg.Done()
		for i := 5; i > 0; i-- {
			l.Lock()
			l.Unlock()
			time.Sleep(1) // <2>
		}
	}

	observer := func(wg *sync.WaitGroup, l sync.Locker) {
		defer wg.Done()
		l.Lock()
		defer l.Unlock()
	}

	test := func(count int, mutex, rwMutex sync.Locker) time.Duration {
		var wg sync.WaitGroup
		wg.Add(count + 1)
		beginTestTime := time.Now()
		go producer(&wg, mutex)
		for i := count; i > 0; i-- {
			go observer(&wg, rwMutex)
		}

		wg.Wait()
		return time.Since(beginTestTime)
	}

	tw := tabwriter.NewWriter(os.Stdout, 0, 1, 2, ' ', 0)
	defer tw.Flush()

	var m sync.RWMutex
	fmt.Fprintf(tw, "Readers\tRWMutext\tMutex\n")
	for i := 0; i < 20; i++ {
		count := int(math.Pow(2, float64(i)))
		fmt.Fprintf(
			tw,
			"%d\t%v\t%v\n",
			count,
			test(count, &m, m.RLocker()),
			test(count, &m, &m),
		)
	}
}
producer 函數:這個函數模擬一個"生產者",它試圖獲得鎖,然後立即釋放它,總共五次。之後它稍微休眠,然後再次嘗試。

observer 函數:這個函數模擬一個"觀察者",它獲得鎖並保持它,直到函數退出。這意味著它會阻止其他goroutines獲得鎖直到它完成。

test 函數:這個函數執行上述的實驗。它啟動一個生產者和多個觀察者goroutines。給定的互斥鎖或讀寫互斥鎖用於同步。它返回從開始到所有goroutines完成所經過的時間。

輸出結果的部分:這段代碼使用 tabwriter 來將結果格式化為表格形式,該表格顯示了觀察者的數量以及使用 RWMutex 和 Mutex 的時間。

實驗循環:循環20次,每次觀察者的數量都在增加(具體來說,每次都是前一次的兩倍)。每次迭代都會呼叫 test 函數兩次:一次使用 RWMutex 的讀鎖,一次使用普通的 Mutex。

最後會輸出
Readers  RWMutext      Mutex
1        87.583µs      4.666µs
2        9.25µs        19.834µs
4        48.334µs      18.083µs
8        18.167µs      28µs
16       31.209µs      20.875µs
32       50.875µs      67.834µs
64       119.916µs     67.166µs
128      119.917µs     144.834µs
256      294.459µs     248.25µs
512      461.958µs     540.666µs
1024     802.542µs     912.416µs
2048     1.03775ms     1.697042ms
4096     2.48025ms     3.901ms
8192     4.091583ms    6.346416ms
16384    6.323875ms    7.706166ms
32768    10.395125ms   5.552167ms
65536    13.514541ms   28.586791ms
131072   44.758875ms   52.467625ms
262144   81.464375ms   115.809625ms
524288   138.851583ms  203.632625ms

Cond

Cond 實現了一個條件變數,是 goroutines 等待或宣告某個事件發生的匯合點。
當你想要等待某種特定條件成立(例如某個資料結構達到某種狀態)時,你可以使用 Cond 來通知其他正在等待的 goroutines。
Cond的用法如下:
Wait(): 等待某個條件成立。在調用 Wait() 之前,必須先鎖定相關的互斥鎖,然後在 Wait() 裡面,這個鎖會被釋放,並且 goroutine 會被阻塞,直到其他地方調用 Signal() 或 Broadcast()。
Signal(): 喚醒一個正在等待的 goroutine。如果有多個 goroutine 正在等待,只有一個會被喚醒。
Broadcast(): 喚醒所有正在等待的 goroutines。

下面為範例程式碼

func main() {
	c := sync.NewCond(&sync.Mutex{})    // <1> 創建一個新的條件變數
	queue := make([]interface{}, 0, 10) // <2> 初始化一個有容量的隊列

	removeFromQueue := func(delay time.Duration) {
		time.Sleep(delay)
		c.L.Lock()        // <8> 鎖定互斥鎖
		queue = queue[1:] // <9> 從隊列中移除元素
		fmt.Println("Removed from queue")
		c.L.Unlock() // <10> 釋放互斥鎖
		c.Signal()   // <11> 通知等待的 goroutine
	}

	for i := 0; i < 10; i++ {
		c.L.Lock()            // <3> 鎖定互斥鎖
		for len(queue) == 2 { // <4> 等待隊列長度不為2的條件
			c.Wait() // <5> 等待條件變數的通知
		}
		fmt.Println("Adding to queue")
		queue = append(queue, struct{}{})
		go removeFromQueue(1 * time.Second) // <6> 啟動 goroutine 來移除隊列中的元素
		c.L.Unlock()                        // <7> 釋放互斥鎖
	}
}


上一篇
6.Goroutine
下一篇
8.Channels
系列文
Concurrency in go 讀書心得30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言